package com.halachawiki.client.login;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.halachawiki.client.login.LoginService.Status;
import com.halachawiki.shared.FieldVerifier;
import com.halachawiki.shared.PublicUserInfo;

public class LoginControl {
	// Start data members (all private)
	private LoginServiceAsync loginService = GWT.create(LoginService.class);
	private LoginConstants Constants = GWT.create(LoginConstants.class);
	private LoginMessages Messages = GWT.create(LoginMessages.class);
	
	// user input
	private String email;
	private String password;
	private boolean rememberMe;
	private String displayName;
	private String aboutMe;
	
	// Exists only after login succeeded.
	private PublicUserInfo userInfo;
	
	// UI root of login control
	private VerticalPanel root;

	enum State {
		UNKNOWN,
		INITIAL,
		GET_PASSWORD,
		NEW_USER,
		BAD_LOGIN,
		GOOD_LOGIN
	};
	State state;
	private AsyncCallback<PublicUserInfo> callback;
	private boolean callbackCalled = false;
	
	// Start public API
	
	/*
	 * parent - parent control for login control
	 * callback - called when login process completes.
	 */
	public LoginControl(Panel parent, AsyncCallback<PublicUserInfo> callback) {
		this.callback = callback;
		state = State.UNKNOWN;
		root = new VerticalPanel();
		parent.add(root);
		refresh();
	}

	// returns null if not logged in.
	public PublicUserInfo getUserInfo() { return userInfo; }

	// refresh state, draw
	private void refresh() {
		currentUserInfo(new AsyncCallback<PublicUserInfo>() {

			@Override
			public void onFailure(Throwable caught) {
				ajaxFailure();
			}

			@Override
			public void onSuccess(PublicUserInfo result) {
				userInfo = result;
				draw();
			}
		});		
	}
	
	public void logout(final Command callback) {
		loginService.logout(new AsyncCallback<Void>() {

			@Override
			public void onFailure(Throwable caught) {
				ajaxFailure();
			}

			@Override
			public void onSuccess(Void result) {
				state=State.INITIAL;
				callbackCalled = false; // in case re-login
				refresh();
				if (callback != null) callback.execute(); 
			}
		});
	}

	
	private void draw() {
		root.addStyleName("login-box");
		if (userInfo.status==Status.NOT_LOGGED_IN) {
			if (state==State.UNKNOWN || state==State.INITIAL) {
				state=State.INITIAL;
				initialScreen();
			} else if (state==State.GET_PASSWORD) {
				passwordScreen();
			} else if (state==State.NEW_USER) {
				newUserScreen();
			}
		} else {
			if (!callbackCalled && callback != null) {
				callback.onSuccess(userInfo);
				callbackCalled = true;
			}
			if (userInfo.status == Status.USER_BANNED) {
				userBanned();
			} else if (userInfo.status == Status.USER_NOT_APPROVED) {
				this.userNotApproved();
			} else if (userInfo.status == Status.USER_NOT_CONFIRMED) {
				String confirmationKey = Window.Location.getParameter("confirm");
				if (confirmationKey != null) {
					// try to confirm user
					loginService.confirmMe(confirmationKey, new AsyncCallback<Status>() {
						@Override
						public void onFailure(Throwable caught) { ajaxFailure(); }
						@Override
						public void onSuccess(Status status) {
							if (status == Status.OK)
								refresh();
							else {
								Window.alert("Incorrect confirmation key");
								userNotConfirmed();
							}
						} 
					} );
				} else {
					userNotConfirmed();
				}
			} else if (userInfo.status==Status.OK){
				root.clear();
				root.setStyleName("");
			} else {
				Window.alert("Unexpected status " + userInfo.status.toString());
			}
		}
	}

	
	// Start private methods
	
	private void ajaxFailure() {
		// TODO improve
		Window.alert("Ajax failure");
	}

	private boolean validateEmail(TextBox email, Label errorLabel) {
		if (!FieldVerifier.isValidEmail(email.getText())) {
			errorLabel.setText(Constants.invalidEmail());
			return false;
		}
		this.email = email.getText();
		return true;
	}
	
	private void initialScreen() {
		VerticalPanel loginPanel = new VerticalPanel();
		root.clear();
		root.add(loginPanel);

		Label header = new Label(Constants.enterEmail()); 
		header.setStyleName("login-label");
		loginPanel.add(header);
		
		FlexTable flexTable = new FlexTable();
		flexTable.setStyleName("login-table");
		loginPanel.add(flexTable);
		
		Label emailLabel = new Label(Constants.emailField());
		flexTable.setWidget(0, 0, emailLabel);
		
		final TextBox email = new TextBox();
		email.setText(this.email);
		email.addStyleName("login-initial-username");
		email.setFocus(true);

		flexTable.setWidget(0, 1, email);
		final Button nextButton = new Button(Constants.nextButton());
		nextButton.addStyleName("login-initial-next");
		flexTable.setWidget(1, 1, nextButton);
		
		final Label errorLabel = new Label();
		errorLabel.addStyleName("login-error-label");
		errorLabel.addStyleName("login-initial-error-label");
		loginPanel.add(errorLabel);

		// behaviors
		nextButton.addClickHandler(new ClickHandler() {

			@Override
			public void onClick(ClickEvent event) {
				nextFromInitial(errorLabel, email);	
			}

		} );
		email.addKeyPressHandler(new KeyPressHandler() {
			@Override
			public void onKeyPress(KeyPressEvent event) {
				if (event.getCharCode()==KeyCodes.KEY_ENTER) {
					nextFromInitial(errorLabel, email);
				}
			} });
}

	private void nextFromInitial(final Label errorLabel, final TextBox email) {
		if (!validateEmail(email, errorLabel))
			return;
		userExists(new AsyncCallback<Boolean>() {

			@Override
			public void onFailure(Throwable caught) {
				// TODO Auto-generated method stub					
			}

			@Override
			public void onSuccess(Boolean result) {
				if (result.booleanValue()) {
					state = State.GET_PASSWORD;
					passwordScreen();
				} else {
					state = State.NEW_USER;
					newUserScreen();
				}
			}
		});
	} 

	boolean validatePassword(PasswordTextBox password, Label errorLabel) {
		if (!FieldVerifier.isValidPassword(password.getText())) {
			errorLabel.setText(Constants.invalidPassword());
			return false;
		}
		this.password = password.getText();
		return true;
	}
	
	void passwordScreen() {
		root.clear();
		VerticalPanel loginPanel = new VerticalPanel();
		root.add(loginPanel);

		Label header = new Label(Constants.welcomeBack()); 
		header.setStyleName("gwt-label-login");
		loginPanel.add(header);
		
		FlexTable flexTable = new FlexTable();
		flexTable.addStyleName("password-table");
		loginPanel.add(flexTable);
	
		flexTable.setWidget(0, 0, new Label(Constants.emailField()));
		flexTable.setWidget(0, 1, new Label(email));

		Label passwordLabel = new Label();
		flexTable.setWidget(1, 0, passwordLabel);
		passwordLabel.setText(Constants.passwordField());
		
		final PasswordTextBox password = new PasswordTextBox();
		flexTable.setWidget(1, 1, password);

		/* TODO: We always 'remember me'.
		CheckBox rememberMe = new CheckBox(Messages.REMEMBER_ME);
		flexTable.setWidget(2, 1, rememberMe); */
		
		final Button nextButton = new Button(Constants.nextButton());
		flexTable.setWidget(3, 1, nextButton);
		nextButton.addStyleName("align-left-button");		

		Button backButton = new Button(Constants.backButton());
		flexTable.setWidget(3, 0, backButton);

		final Label errorLabel = new Label();
		errorLabel.addStyleName("login-error-label");
		loginPanel.add(errorLabel);
		
		Label forgotPassword = new Label(Constants.forgotPassword());
		forgotPassword.addStyleName("clickable-link");
		loginPanel.add(forgotPassword);

		password.setFocus(true);

		backButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				state=State.INITIAL;
				draw();
			} 
		} );

		nextButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				passwordNext(password, errorLabel);
			}
		} );

		password.addKeyPressHandler(new KeyPressHandler() {
			@Override
			public void onKeyPress(KeyPressEvent event) {
				char key = event.getCharCode();
				if (key == KeyCodes.KEY_ENTER)
					passwordNext(password, errorLabel);
			}
		});

		forgotPassword.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				forgotPassword(errorLabel);
			}
		});

		/* TODO: Remember me.
		final LoginControl loginPage = this;
		rememberMe.addValueChangeHandler(new ValueChangeHandler<Boolean>() {

			@Override
			public void onValueChange(ValueChangeEvent<Boolean> event) {
				loginPage.rememberMe = event.getValue();
			} }); */


	}
	
	private void passwordNext(final PasswordTextBox password,
			final Label errorLabel) {
		if (!validatePassword(password, errorLabel))
			return;
		login(new AsyncCallback<Status>() {
			@Override
			public void onFailure(Throwable caught) {
				ajaxFailure();
			}

			@Override
			public void onSuccess(Status result) {
				if (result == Status.WRONG_PASSWORD)
					errorLabel.setText(Constants.wrongPassword());
				else
					refresh();
			} 
		});
	}
	
	void newUserScreen() {
		VerticalPanel loginPanel = new VerticalPanel();
		final Label errorLabel = new Label();
		Label header = new Label(); 
		header.setStyleName("gwt-label-login");
		header.setText(Constants.pleaseRegister());
		loginPanel.add(header);
		
		FlexTable flexTable = new FlexTable();
		loginPanel.add(flexTable);
	
		flexTable.setWidget(0, 0, new Label(Constants.emailField()));
		flexTable.setWidget(0, 1, new Label(email));

		Label passwordLabel = new Label();
		flexTable.setWidget(1, 0, passwordLabel);
		passwordLabel.setText(Constants.passwordField());
		
		final PasswordTextBox password = new PasswordTextBox();
		final PasswordTextBox confirmPassword = new PasswordTextBox();
		flexTable.setWidget(1, 1, password);
		password.addKeyPressHandler(new KeyPressHandler() {
			@Override
			public void onKeyPress(KeyPressEvent event) {
				if (event.getCharCode()==KeyCodes.KEY_ENTER) {
					confirmPassword.setFocus(true);
				}
			} });

		Label confirmPasswordLabel = new Label(Constants.confirmPasswordField());
		flexTable.setWidget(2, 0, confirmPasswordLabel);
		flexTable.setWidget(2, 1, confirmPassword);
		
		Label displayNameLabel = new Label(Constants.displayNameField());
		final TextBox displayName = new TextBox();
		confirmPassword.addKeyPressHandler(new KeyPressHandler() {
			@Override
			public void onKeyPress(KeyPressEvent event) {
				if (event.getCharCode()==KeyCodes.KEY_ENTER) {
					displayName.setFocus(true);
				}
			} });
		
		flexTable.setWidget(3, 0, displayNameLabel);
		flexTable.setWidget(3, 1, displayName);
		
		Label aboutMeLabel = new Label(Constants.aboutMeField());
		final TextArea aboutMe = new TextArea();
		flexTable.setWidget(4, 0, aboutMeLabel);
		flexTable.setWidget(4, 1, aboutMe);
		displayName.addKeyPressHandler(new KeyPressHandler() {
			@Override
			public void onKeyPress(KeyPressEvent event) {
				if (event.getCharCode()==KeyCodes.KEY_ENTER) {
					aboutMe.setFocus(true);
				}
			} });
		
		Button backButton = new Button(Constants.backButton());
		backButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				state=State.INITIAL;
				draw();
			}
		} );
		flexTable.setWidget(5, 0, backButton);

		final Button nextButton = new Button(Constants.nextButton());
		final LoginControl loginPage = this;
		nextButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (!validatePassword(password, errorLabel)) {
					password.setFocus(true);
					return;
				}
				if (!password.getText().equals(confirmPassword.getText())) {
					errorLabel.setText(Constants.passwordsDontMatch());
					password.setFocus(true);
					return;
				}
				if (displayName.getText().isEmpty()) {
					errorLabel.setText(Constants.pleaseEnterDisplayName());
					displayName.setFocus(true);
					return;
				}
				loginPage.displayName = displayName.getText();
				loginPage.aboutMe = aboutMe.getText();
				newUser(new AsyncCallback<Status>() {

					@Override
					public void onFailure(Throwable caught) {
						ajaxFailure();
					}

					@Override
					public void onSuccess(Status result) {
						if (result == Status.OK)
							refresh();
						if (result == Status.GENERIC_ERROR)
							errorLabel.setText("An error has occurred");
					} });
			} } );
		flexTable.setWidget(5, 1, nextButton);
		aboutMe.addKeyPressHandler(new KeyPressHandler() {
			@Override
			public void onKeyPress(KeyPressEvent event) {
				if (event.getCharCode()==KeyCodes.KEY_ENTER && aboutMe.getText().isEmpty()) {
					nextButton.setFocus(true);
				}
				if (event.getCharCode()==KeyCodes.KEY_TAB) {
					nextButton.setFocus(true);
				}
			} });
		
		loginPanel.add(errorLabel);
				
		root.clear();
		root.add(loginPanel);

		password.setFocus(true);

	}
	
	
	// various server requests
	private void forgotPassword(final Label errorLabel) {
		loginService.sendPasswordReminder(email, new AsyncCallback<Status>() {

			@Override
			public void onFailure(Throwable caught) {
				ajaxFailure();
			}

			@Override
			public void onSuccess(Status result) {
				if (result == Status.OK) {
					errorLabel.setText(Constants.passwordReminderSent());
				} else {
					errorLabel.setText(Constants.passwordReminderError());
				}
				// TODO Auto-generated method stub
				
			} } );
	}
	
	private void userBanned() {
		root.clear();
		VerticalPanel vp = new VerticalPanel();
		root.add(vp);
		Label statusLabel = new Label(Messages.banned(userInfo.displayName));
		statusLabel.setWordWrap(true);
		vp.add(statusLabel);
		Button logoutButton = new Button();
		logoutButton.setText(Constants.logoutButton());
		vp.add(logoutButton);
		logoutButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				logout(null);
			}
		});
		/* TODO: add 'send mail to admin' link */
	}
	
	private void userNotApproved() {
		root.clear();
		VerticalPanel vp = new VerticalPanel();
		root.add(vp);
		Label statusLabel = new Label(Messages.notApproved(userInfo.displayName));
		statusLabel.setWordWrap(true);
		vp.add(statusLabel);
		Button logoutButton = new Button();
		logoutButton.setText(Constants.logoutButton());
		vp.add(logoutButton);
		logoutButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				logout(null);
			}
		});
		/* TODO: add 'send mail to admin' link */
	}

	private void userNotConfirmed() {
		root.clear();
		VerticalPanel vp = new VerticalPanel();
		root.add(vp);
		Label statusLabel = new Label(Messages.notConfirmed(userInfo.displayName));
		statusLabel.setWordWrap(true);
		vp.add(statusLabel);
		Button logoutButton = new Button();
		logoutButton.setText(Constants.logoutButton());
		vp.add(logoutButton);
		Label resendConfirmation = new Label(Constants.resendConfirmationEmail());
		vp.add(resendConfirmation);
		resendConfirmation.setStyleName("clickable-link");
		
		logoutButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				logout(null);
			}
		});
		resendConfirmation.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				loginService.resendConfirmationEmail(new AsyncCallback<Boolean>() {
					@Override
					public void onFailure(Throwable caught) { ajaxFailure(); }
					@Override
					public void onSuccess(Boolean result) {
						Window.alert(Constants.confirmationMailSent());
					} 
				});
			} 
		});
	}
	
	void currentUserInfo(AsyncCallback<PublicUserInfo> callback) {
		loginService.getCurrentUserInfo(callback);
	}
	
	void userExists(final AsyncCallback<Boolean> callback) {
		loginService.userExists(email, new AsyncCallback<Status>() {

			@Override
			public void onFailure(Throwable caught) {
				ajaxFailure();
			}

			@Override
			public void onSuccess(Status result) {
				callback.onSuccess(result == Status.OK);
			} });
	}
	
	private void login(final AsyncCallback<Status> callback) {
		loginService.logInUser(email, password, rememberMe, callback);
	}
	
	private void newUser(final AsyncCallback<Status> callback) {
		loginService.registerNewUser(email, password, displayName, aboutMe, Constants.locale(), callback);
	}
}
